/*********************************************************************************
 *      Copyright:  (C) 2022 dairongan<2974932606@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  i2c_OLED.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(07/21/2022)
 *         Author:  dairongan <2974932606@qq.com>
 *      ChangeLog:  1, Release initial version on "07/21/2022 11:35:53 AM"
 *                 
 ********************************************************************************/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/spi/spi.h>
#include <linux/mm.h>

#define OLED_CNT		1
#define OLED_NAME		"OLED"
#define OLED_ADDR		0x3c	//发送控制命令
#define OLED_DATA_MODE	0x40	//发送显示的数据
#define OLED_CMD_MODE	0x00
#define OLED_SETPOS	 	3
#define Max_Column		128


struct oled_dev{
	dev_t	devid;
	struct	cdev cdev;
	struct	class *class;
	struct	device *device;
	struct	device_node *nd;
	int 	major;
	void	*private_data;
};

unsigned char *oled_data;	//用作缓冲的全局变量
static struct oled_dev oleddev;

//发送一个字节的数据给oled,可选这此数据是命令还是需要显示的数据
static int oled_write_one_data_or_cmd(struct i2c_client *client,unsigned char buf,unsigned char cmd)
{
	int rv=0;

	char data[2]={0};

	if(!client)
	{
		printk("%s line [%d] %s() get invalid input arguments\r\n", __FILE__, __LINE__, __func__);
		return -1;
	}

	data[0]=cmd;
	data[1]=buf;

	rv = i2c_master_send(client,data,2);
	if(rv<0)
	{
		dev_err(&client->dev,"i2c send cmd failure\r\n");
		return -1;
	}

	return 0;
}

static void oled_set_inital_position(struct i2c_client *client,unsigned char x,unsigned char y)
{
	oled_write_one_data_or_cmd(client,0xB0+y,OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client,((x&0xF0)>>4)|0x10,OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client,(x&0x0F)|0x01,OLED_CMD_MODE);
}
static void oled_clear(struct i2c_client *client)
{
    int n = 0;
    int x_axis = 0;
    int y_axis = 0;
    for(y_axis=0; y_axis<8; y_axis++)
    {
        oled_set_inital_position(client, 0, y_axis);
        for(x_axis=0; x_axis<128; x_axis++)
        {
            oled_write_one_data_or_cmd(client,0x00, OLED_DATA_MODE);
            n++;
        }
    }
}



static void oled_reinit(struct i2c_client *client)
{
 	oled_write_one_data_or_cmd(client, 0x10, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0xB0, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0xC8, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x00, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x10, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x10, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x40, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x81, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0xDF, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0xA1, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0xA6, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0xA8, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x3F, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0xA4, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0xD3, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x00, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0xD5, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0xF0, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0xD9, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x22, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0xDA, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x12, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0xDB, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x20, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x8D, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x14, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0xAF, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x20, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x00, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0xB0, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x00, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x10, OLED_CMD_MODE);
	oled_write_one_data_or_cmd(client, 0x00, OLED_CMD_MODE);
	oled_clear(client);
}

static int oled_open(struct inode *inode,struct file *filp)
{

	filp->private_data = &oleddev;
	return 0;
}

static ssize_t oled_read(struct file *filp,char __user *buf,size_t cnt,loff_t *off)
{
	struct oled_dev *dev = (struct oled_dev *)filp->private_data;
	int rv=0;

	rv = copy_to_user(buf,oled_data,cnt);
	if(rv<0)
	{
		dev_err(dev->device,"copy to user error!\r\n");
		return -EFAULT;
	}

	return 0;
}

static ssize_t oled_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *off)
{
	
	struct oled_dev *dev = (struct oled_dev *)filp->private_data;
	int rv = 0;
	unsigned char databuf[1024];
	
	rv=copy_from_user(databuf,buf,cnt);
	if(rv<0)
	{
		dev_err(dev->device,"copy from user error!\r\n");
		return -EFAULT;
	}


	return 0;
}

static int oled_mmap(struct file *filp,struct vm_area_struct *vma)
{
	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

	if(remap_pfn_range(vma,vma->vm_start,virt_to_phys(oled_data)>>PAGE_SHIFT,
				vma->vm_end-vma->vm_start,vma->vm_page_prot))
	{
		return -ENOBUFS;
	}

	return 0;
}

static long oled_ioctrl(struct file *filp,unsigned int cmd,unsigned long arg)
{
	struct oled_dev *dev = filp->private_data;

	switch(cmd)
	{
		case OLED_DATA_MODE:
			oled_write_one_data_or_cmd(dev->private_data,(unsigned char)arg,OLED_DATA_MODE);
			break;
		case OLED_CMD_MODE:
			oled_write_one_data_or_cmd(dev->private_data,(unsigned char)arg,OLED_CMD_MODE);
			break;
		case OLED_SETPOS:
			oled_set_inital_position(dev->private_data,arg/10,arg%10);
			break;
	}
	return 0;
}

static int oled_release(struct inode *inode,struct file *filp)
{
	return 0;
}

static const struct file_operations oled_ops = {
	.owner = THIS_MODULE,
	.open = oled_open,
	.read = oled_read,
	.write = oled_write,
	.mmap = oled_mmap,
	.unlocked_ioctl = oled_ioctrl,
	.release = oled_release,
};


static int oled_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
	if(oleddev.major)
	{
		oleddev.devid = MKDEV(oleddev.major,0);
		register_chrdev_region(oleddev.devid,OLED_CNT,OLED_NAME);
	}
	else
	{
		alloc_chrdev_region(&oleddev.devid,0,OLED_CNT,OLED_NAME);
		oleddev.major = MAJOR(oleddev.devid);
	}

	cdev_init(&oleddev.cdev,&oled_ops);
	cdev_add(&oleddev.cdev,oleddev.devid,OLED_CNT);

	oleddev.class = class_create(THIS_MODULE,OLED_NAME);
	if(IS_ERR(oleddev.class))
	{
		return PTR_ERR(oleddev.class);
	}

	oleddev.device = device_create(oleddev.class,NULL,oleddev.devid,NULL,OLED_NAME);
	if(IS_ERR(oleddev.device))
	{
		return PTR_ERR(oleddev.device);
	}

	oled_data = kmalloc(1024,GFP_KERNEL);
	SetPageReserved(virt_to_page(oled_data));

	oled_reinit(client);

	oleddev.private_data=client;

	printk("insmod successfully!\r\n");
	return 0;
}

static int oled_remove(struct i2c_client *client)
{
	ClearPageReserved(virt_to_page(oled_data));
	cdev_del(&oleddev.cdev);
	unregister_chrdev_region(oleddev.devid,OLED_CNT);

	device_destroy(oleddev.class,oleddev.devid);
	class_destroy(oleddev.class);
	kfree(oled_data);

	return 0;
}


static const struct i2c_device_id oled_id[]={
	{"ssr,oled",0},
	{}
};

static const struct of_device_id oled_of_match[]={
	{.compatible = "ssr,oled"},
	{}
};

static struct i2c_driver oled_driver = {
	.probe = oled_probe,
	.remove = oled_remove,
	.driver = {
		.owner = THIS_MODULE,
		.name = "oled",
		.of_match_table = oled_of_match,
	},
	.id_table = oled_id,
};

static int __init oled_init(void)
{
	return i2c_add_driver(&oled_driver);

}

static void __exit oled_exit(void)
{
	i2c_del_driver(&oled_driver);
}

module_init(oled_init);
module_exit(oled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dairongan");
